﻿

namespace Patterns4Net.ArchitectureExplorer.Diagram.Services
{
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using Mono.Cecil;
    using Attributes;
    using Core;
    using Core.AbstractionLevel;
    using Relations;

    /// <summary>
    /// This provider adds implemented patterns to types and methods.
    /// It should be executed before the <see cref="PatternRolesProvider"/>.
    /// </summary>
    [DiagramUpdaterExport(Order = 0)]
    public class PatternUpdater : DiagramVisitor, IDiagramUpdater
    {
        private readonly PatternsLocator patternsLocator;

        private readonly IPatternLevelProvider patternLevelProvider;

        [ImportingConstructor]
        public PatternUpdater(
            PatternsLocator patternsLocator,
            IPatternLevelProvider patternLevelProvider)
        {
            this.patternsLocator = patternsLocator;
            this.patternLevelProvider = patternLevelProvider;
        }

        public void UpdateDiagram(Diagram diagram, TextWriter writer)
        {
            diagram.Accept(this);
        }

        public override void Visit(TypeInfo typeInfo)
        {
            if (typeInfo.Patterns.Any())
            {
                // patterns have been already added.
                return;
            }

            var typeDef = typeInfo.GetTag<TypeDefinition>();
            if (typeDef == null)
            {
                return;
            }

            this.patternsLocator.GetPatternsOf(typeDef)
                .Select(pattern =>
                    new PatternInfo(
                            pattern.Name,
                            pattern,
                            this.GetLevel(pattern)
                        ))
                .ForEach(typeInfo.AddPattern);
        }

        public override void Visit(MethodInfo methodInfo)
        {
            if (methodInfo.Patterns.Any())
            {
                // patterns have been already added.
                return;
            }

            var methodDef = methodInfo.GetTag<MethodDefinition>();
            if (methodDef == null)
            {
                return;
            }

            this.patternsLocator.GetPatternsOf(methodDef)
                .Select(pattern =>
                    new PatternInfo(
                            pattern.Name,
                            pattern,
                            this.GetLevel(pattern)
                        ))
                .ForEach(methodInfo.AddPattern);
        }

        private AbstractionLevel GetLevel(IPattern pattern)
        {
            var result = this.patternLevelProvider.GetLevel(pattern);
            if (result.HasValue)
            {
                return result.Value;
            }

            return AbstractionLevel.Normal;
        }
    }
}
